package com.ld.shieldsb.jgit;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.LogCommand;
import org.eclipse.jgit.api.LsRemoteCommand;
import org.eclipse.jgit.api.PullCommand;
import org.eclipse.jgit.api.PullResult;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.blame.BlameResult;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.diff.EditList;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.RevisionSyntaxException;
import org.eclipse.jgit.errors.StopWalkException;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.patch.FileHeader;
import org.eclipse.jgit.patch.HunkHeader;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.AndRevFilter;
import org.eclipse.jgit.revwalk.filter.MaxCountRevFilter;
import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.revwalk.filter.SkipRevFilter;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.gitective.core.BlobUtils;

import com.ld.shieldsb.common.core.collections.ListUtils;
import com.ld.shieldsb.common.core.reflect.ClassUtil;
import com.ld.shieldsb.common.core.util.StringUtils;
import com.ld.shieldsb.common.core.util.date.DateUtil;
import com.ld.shieldsb.jgit.exception.JgitNologException;
import com.ld.shieldsb.jgit.model.BranchNameList;
import com.ld.shieldsb.jgit.model.BranchNameModel;
import com.ld.shieldsb.jgit.model.File2CommitChangeModel;
import com.ld.shieldsb.jgit.model.FileBlameModel;
import com.ld.shieldsb.jgit.model.GitCommitChangeModel;
import com.ld.shieldsb.jgit.model.GitCommitInfoModel;
import com.ld.shieldsb.jgit.model.LineDiff;
import com.ld.shieldsb.jgit.model.QueryParams;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class GitUtil {
    private GitUtil() {
        throw new IllegalStateException("工具类不可实例化");
    }

    public static final String GIT = ".git";

    /**
     * 根据记录号获取发生改变的文件列表
     * 
     * @Title getChangedFileList
     * @author 吕凯
     * @date 2021年6月7日 上午11:13:42
     * @param revCommit
     * @param repo
     * @return List<DiffEntry>
     */
    public static List<DiffEntry> getChangedFileList(RevCommit revCommit, Repository repo) {
        List<DiffEntry> returnDiffs = null;
        try {
            RevCommit previsouCommit = getPrevHash(revCommit, repo); // 获取上一次提交记录
            ObjectId head = revCommit.getTree().getId();

            ObjectId oldHead = null;
            if (previsouCommit != null) {
                oldHead = previsouCommit.getTree().getId();
                log.warn("比较差异的版本号: {} and {}", revCommit.getName(), previsouCommit.getName());
            }

            // prepare the two iterators to compute the diff between
            try (ObjectReader reader = repo.newObjectReader()) {
                CanonicalTreeParser oldTreeIter = null;
                if (oldHead != null) {
                    oldTreeIter = new CanonicalTreeParser();
                    oldTreeIter.reset(reader, oldHead);
                }
                CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
                newTreeIter.reset(reader, head);

                // finally get the list of changed files
                try (Git git = new Git(repo)) {
                    List<DiffEntry> diffs = git.diff().setNewTree(newTreeIter).setOldTree(oldTreeIter).call();
                    returnDiffs = diffs;

                    /*try (TreeWalk treeWalk = new TreeWalk(repo)) {
                        treeWalk.reset(head);
                        while (treeWalk.next()) {
                            String path = treeWalk.getPathString();
                            System.out.println(path);
                        }
                    }*/

                } catch (GitAPIException e) {
                    log.error("", e);
                }
            }
        } catch (IOException e) {
            log.error("", e);
        }
        return returnDiffs;
    }

    /**
     * 获取上次提交记录
     * 
     * @Title getPrevHash
     * @author 吕凯
     * @date 2021年6月7日 上午11:15:21
     * @param commit
     * @param repo
     * @return
     * @throws IOException
     *             RevCommit
     */
    public static RevCommit getPrevHash(RevCommit commit, Repository repo) throws IOException {

        try (RevWalk walk = new RevWalk(repo)) {
            // Starting point
            walk.markStart(commit);
            int count = 0;
            for (RevCommit rev : walk) {
                // got the previous commit.
                if (count == 1) {
                    return rev;
                }
                count++;
            }
            walk.dispose();
        }
        // Reached end and no previous commits.
        return null;
    }

    /**
     * 列出两次提交的差异
     * 
     * @Title listDiff
     * @author 吕凯
     * @date 2021年6月10日 上午11:47:41
     * @param repository
     * @param git
     * @param oldCommit
     * @param newCommit
     * @throws GitAPIException
     * @throws IOException
     *             void
     */
    public static List<DiffEntry> listDiff(Repository repository, Git git, String oldCommit, String newCommit)
            throws GitAPIException, IOException {
        return git.diff().setOldTree(prepareTreeParser(repository, oldCommit)).setNewTree(prepareTreeParser(repository, newCommit)).call();

    }

    /**
     * 转换为树对象
     * 
     * @Title prepareTreeParser
     * @author 吕凯
     * @date 2021年6月10日 上午11:47:22
     * @param repository
     * @param objectId
     * @return
     * @throws IOException
     *             AbstractTreeIterator
     */
    public static AbstractTreeIterator prepareTreeParser(Repository repository, String objectId) throws IOException {
        // from the commit we can build the tree which allows us to construct the TreeParser
        // noinspection Duplicates
        try (RevWalk walk = new RevWalk(repository)) {
            RevCommit commit = walk.parseCommit(repository.resolve(objectId));
            RevTree tree = walk.parseTree(commit.getTree().getId());

            CanonicalTreeParser treeParser = new CanonicalTreeParser();
            try (ObjectReader reader = repository.newObjectReader()) {
                treeParser.reset(reader, tree.getId());
            }

            walk.dispose();

            return treeParser;
        }
    }

    /**
     * 获取git仓库下所有的分支，不需要下载
     * 
     * @Title getRemoteBranchs
     * @author 吕凯
     * @date 2021年6月7日 上午11:15:45
     * @param url
     *            仓库url
     * @param username
     *            用户名
     * @param password
     *            密码
     * @return List<String> 分支名称list
     */

    public static List<String> getRemoteBranchs(String url, String username, String password) {
        List<String> branchnameList = new ArrayList<>();
        try {
            LsRemoteCommand command = Git.lsRemoteRepository().setHeads(true) // 查询分支
                    .setRemote(url);

            if (StringUtils.isNotEmpty(username) && StringUtils.isNotEmpty(password)) {
                UsernamePasswordCredentialsProvider pro = new UsernamePasswordCredentialsProvider(username, password);
                command = command.setCredentialsProvider(pro);
            }
            Collection<Ref> refList = command.call();
            for (Ref ref : refList) {
                log.warn("ref:::{}", ref);
                String refName = ref.getName();
                if (refName.startsWith("refs/heads/")) { // 需要进行筛选
                    String branchName = refName.replace("refs/heads/", "");
                    branchnameList.add(branchName);
                }
            }

            Map<String, Ref> map = command.callAsMap();
            for (Map.Entry<String, Ref> entry : map.entrySet()) {
                log.warn("entry:::{}={}", entry.getKey(), entry.getValue());
            }

            log.warn("共有分支 {} 个", branchnameList.size());

        } catch (Exception e) {
            log.error("error", e);
        }
        return branchnameList;
    }

    // =============================================================================================

    /**
     * 克隆远程项目到本地
     * 
     * @Title gitClone
     * @author 吕凯
     * @date 2021年6月7日 下午2:54:44
     * @param username
     *            用户名
     * @param password
     *            密码
     * @param remotePath
     *            远程地址
     * @param branch
     *            分支
     * @param localPath
     *            本地地址
     * @throws IOException
     * @throws GitAPIException
     * @return void
     */
    public static void gitClone(String username, String password, String remotePath, String branch, String localPath)
            throws GitAPIException {
        long start = System.currentTimeMillis();
        log.warn("开始下载项目:{},分支{},本地地址：{}！", remotePath, branch, localPath);
        // 克隆代码库命令
        CloneCommand cloneCommand = Git.cloneRepository();
        // 设置远程URI
        cloneCommand.setURI(remotePath)
                // 设置clone下来的分支
                .setBranch(branch)
                // 设置下载存放路径
                .setDirectory(new File(localPath));
        // 设置权限验证
        if (StringUtils.isNotEmpty(username) && StringUtils.isNotEmpty(password)) {
            // 设置远程服务器上的用户名和密码
            UsernamePasswordCredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password);
            cloneCommand = cloneCommand.setCredentialsProvider(credentialsProvider);
        }
        cloneCommand.call();
        long end = System.currentTimeMillis();
        log.warn("下载项目完成:{}，共用时{}ms！", remotePath, end - start);
    }

    /**
     * 
     * 拉取远程仓库内容并合并至本地
     * 
     * @Title gitPull
     * @author 吕凯
     * @date 2021年6月7日 下午3:03:10
     * @param username
     *            git用户名
     * @param password
     *            git密码
     * @param localPath
     *            本地仓库路径
     * @param branch
     *            分支
     * @throws IOException
     * @throws GitAPIException
     *             void
     */
    public static void gitPull(String username, String password, String localPath, String branch) throws IOException, GitAPIException {
        long start = System.currentTimeMillis();
        log.warn("pull 仓库：{}  {} 分支的代码开始", localPath, branch);
        // git仓库地址
        try (Git git = new Git(new FileRepository(localPath + "/" + GIT));) {

            System.out.println(getCurrentBranch(git));
            log.warn("branch==" + branch);
            PullCommand command = git.pull().setRemoteBranchName(branch);
            System.out.println(getCurrentBranch(git));
            // 设置权限验证
            if (StringUtils.isNotEmpty(username) && StringUtils.isNotEmpty(password)) {
                // 设置远程服务器上的用户名和密码
                UsernamePasswordCredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password);
                command = command.setCredentialsProvider(credentialsProvider);
            }
            PullResult result = command.call();
            log.warn("pull结果：{}", result.getFetchResult().getMessages());
            log.warn("pull结果：{}", result);
        }
        long end = System.currentTimeMillis();
        log.warn("pull 仓库：{}  {} 分支的代码完成，共用时{}ms！", localPath, branch, end - start);
    }

    /**
     * 获取本地的当前分支
     * 
     * @Title getCurrentBranch
     * @author 吕凯
     * @date 2021年6月7日 下午3:07:14
     * @param localPath
     * @return
     * @throws IOException
     *             String
     */
    public static String getCurrentBranch(String localPath) throws IOException {
        try (Git git = Git.open(new File(localPath));) {
            return getCurrentBranch(git);
        }
    }

    public static String getCurrentBranch(Git git) throws IOException {
        return getCurrentBranch(git.getRepository());
    }

    public static String getCurrentBranch(Repository repo) throws IOException {
        return repo.getBranch();
    }

    /**
     * 通过本地仓库获取所有的远程分支完整名称[refs/remotes/origin/master,refs/remotes/origin/release]
     * 
     * @Title getAllBranch
     * @author 吕凯
     * @date 2021年6月7日 下午3:08:44
     * @param localPath
     *            仓库名称
     * 
     * @throws IOException
     * @throws GitAPIException
     * @return List<String>
     */
    public static List<String> getAllBranch(String localPath) throws IOException, GitAPIException {
        List<String> result = new LinkedList<>();
        // 获取名称
        try (Git git = Git.open(new File(localPath));) {
            StoredConfig storedConfig = git.getRepository().getConfig();
            String currentRemote = storedConfig.getString("branch", getCurrentBranch(localPath), "remote");
            String index = "refs/remotes/" + currentRemote;
            List<Ref> refList = git.getRepository().getRefDatabase().getRefs();
            log.warn("{}", refList);
            refList.stream().filter(ref -> ref.getName().indexOf(index) > -1).forEach(ref -> result.add(ref.getName()));
        }
        log.warn("result={}", result);
        return result;
    }

    /**
     * 
     * 比较两个版本之间文件的差异
     * 
     * @Title gitdiff
     * @author 吕凯
     * @date 2021年6月7日 下午3:09:39
     * @param commitList
     * @param localPath
     * @return
     * @throws IOException
     * @throws GitAPIException
     *             Map<String,Object>
     */
    @SuppressWarnings("unchecked")
    public static LineDiff gitDiff(List<RevCommit> commitList, String localPath) throws IOException, GitAPIException {
        LineDiff map = new LineDiff();
        try (Git git = new Git(new FileRepository(localPath + "/" + GIT));
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                DiffFormatter df = new DiffFormatter(out);
                Repository repository = git.getRepository();) {

            AbstractTreeIterator newTree = prepareTreeParser(commitList.get(0), repository);
            AbstractTreeIterator oldTree = prepareTreeParser(commitList.get(1), repository);
            List<DiffEntry> diff = git.diff().setOldTree(oldTree).setNewTree(newTree).setShowNameAndStatusOnly(true).call();

            // 设置比较器为忽略空白字符对比（Ignores all whitespace）
            df.setDiffComparator(RawTextComparator.WS_IGNORE_ALL);
            df.setRepository(git.getRepository());

            // 每一个diffEntry都是第个文件版本之间的变动差异
            int addSize = 0;
            int subSize = 0;
            for (DiffEntry diffEntry : diff) {
                // 打印文件差异具体内容
                df.format(diffEntry);
//                String diffText = out.toString("UTF-8");

                // 获取文件差异位置，从而统计差异的行数，如增加行数，减少行数
                FileHeader fileHeader = df.toFileHeader(diffEntry);
                List<HunkHeader> hunks = (List<HunkHeader>) fileHeader.getHunks();

                for (HunkHeader hunkHeader : hunks) {
                    EditList editList = hunkHeader.toEditList();
                    for (Edit edit : editList) {
                        subSize += edit.getEndA() - edit.getBeginA();
                        addSize += edit.getEndB() - edit.getBeginB();

                    }
                }
                out.reset();
            }
            map.setAddLine(addSize);
            map.setRemoveLine(subSize);
        }
        return map;
    }

    @SuppressWarnings("unchecked")
    public static LineDiff gitLineDiff(List<DiffEntry> diff, String localRepository) throws IOException {
        LineDiff map = new LineDiff();

        FileRepositoryBuilder builder = newFileRepoBuilder(localRepository);

        try (Repository repo = builder.build();
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                DiffFormatter df = new DiffFormatter(out);) {

            // 设置比较器为忽略空白字符对比（Ignores all whitespace）
            df.setDiffComparator(RawTextComparator.WS_IGNORE_ALL);
            df.setRepository(repo);

            // 每一个diffEntry都是第个文件版本之间的变动差异
            int addSize = 0;
            int subSize = 0;
            for (DiffEntry diffEntry : diff) {
                // 打印文件差异具体内容
                df.format(diffEntry);
//                String diffText = out.toString("UTF-8");

                // 获取文件差异位置，从而统计差异的行数，如增加行数，减少行数
                FileHeader fileHeader = df.toFileHeader(diffEntry);
                List<HunkHeader> hunks = (List<HunkHeader>) fileHeader.getHunks();

                for (HunkHeader hunkHeader : hunks) {
                    EditList editList = hunkHeader.toEditList();
                    for (Edit edit : editList) {
                        subSize += edit.getEndA() - edit.getBeginA();
                        addSize += edit.getEndB() - edit.getBeginB();

                    }
                }
                out.reset();
            }
            map.setAddLine(addSize);
            map.setRemoveLine(subSize);
        }
        return map;
    }

    /**
     * 根据路径new文件构造器
     * 
     * @Title newFileRepoBuilder
     * @author 吕凯
     * @date 2021年7月27日 下午4:50:30
     * @param localRepository
     * @return FileRepositoryBuilder
     */
    public static FileRepositoryBuilder newFileRepoBuilder(String localRepository) {
        String path = localRepository;// 对应项目在本地Repo的路径
        FileRepositoryBuilder builder = new FileRepositoryBuilder();
        builder.setMustExist(true);
        builder.addCeilingDirectory(new File(path));
        builder.findGitDir(new File(path));
        return builder;
    }

    private static AbstractTreeIterator prepareTreeParser(RevCommit commit, Repository repository) {
        try (RevWalk walk = new RevWalk(repository)) {
            RevTree tree = walk.parseTree(commit.getTree().getId());

            CanonicalTreeParser oldTreeParser = new CanonicalTreeParser();
            try (ObjectReader oldReader = repository.newObjectReader()) {
                oldTreeParser.reset(oldReader, tree.getId());
            }

            walk.dispose();

            return oldTreeParser;
        } catch (IOException e) {
            log.error("", e);
        }
        return null;
    }

    /**
     * 获取指定版本行数
     * 
     * @Title getAllFileLines
     * @author 吕凯
     * @date 2021年6月7日 下午4:34:29
     * @param commit
     * @param localPath
     * @return int
     */
    public static int getAllFileLines(RevCommit commit, String localPath) {
        int size = 0;
        try (Git git = new Git(new FileRepository(localPath + "/" + GIT));
                Repository repository = git.getRepository();
                TreeWalk treeWalk = new TreeWalk(repository);) {

            treeWalk.addTree(commit.getTree());
            treeWalk.setRecursive(true);
            MutableObjectId id = new MutableObjectId();
            while (treeWalk.next()) {
                treeWalk.getObjectId(id, 0);
                int lines = countAddLine(BlobUtils.getContent(repository, id.toObjectId()));
                size += lines;
            }
        } catch (IOException e) {
            log.error("", e);
        }
        return size;
    }

    /**
     * 统计非空白行数
     * 
     * @param content
     * @return
     */
    public static int countAddLine(String content) {
        char[] chars = content.toCharArray();
        int sum = 0;
        boolean notSpace = false;
        for (char ch : chars) {
            if (ch == '\n' && notSpace) {
                sum++;
                notSpace = false;
            } else if (ch > ' ') {
                notSpace = true;
            }
        }
        // 最后一行没有换行时，如果有非空白字符，则+1
        if (notSpace) {
            sum++;
        }
        return sum;
    }

    /**
     * 获取本地git某个仓库下当前分支下所有的更新记录,会多返回1条记录，用以判断是否为最后一页
     * 
     * @Title getGitVersions
     * @author 吕凯
     * @date 2021年6月9日 上午8:26:28
     * @return List<GitCommitInfoModel>
     * @throws GitAPIException
     * @throws IOException
     * @throws AppException
     * 
     */
    public static List<GitCommitInfoModel> getGitVersions(String localPath, String projectName, QueryParams params)
            throws GitAPIException, JgitNologException, IOException {
        List<GitCommitInfoModel> list = new ArrayList<>();
        if (StringUtils.isEmpty(projectName)) {
            projectName = StringUtils.substringAfterLast(localPath, File.separator);
        }
        try (Git git = new Git(new FileRepository(localPath + "/" + GIT));
                Repository repo = git.getRepository();
                RevWalk walk = new RevWalk(repo);) {
            LogCommand logCommand = git.log(); // .add(repository.resolve("remotes/origin/testbranch")) // 指定分支
            String branch = params.getBranch();
            String commiter = params.getCommiter();

            String message = params.getMessage();
            Integer pageNum = ClassUtil.obj2int(params.getPageNum());
            Integer pageSize = ClassUtil.obj2int(params.getPageSize());
            if (StringUtils.isNotEmpty(branch)) {
                logCommand.add(git.getRepository().resolve(branch)); // 指定分支
            } else {
                logCommand.all();
            }
            List<RevFilter> filters = new ArrayList<>();
            // 需要过滤的先添加，过滤器的顺序会影响结果
            if (StringUtils.isNotEmpty(commiter) || StringUtils.isNotEmpty(message)) {
                RevFilter filter = new RevFilter() {
                    @Override
                    public boolean include(RevWalk walker, RevCommit cmit) throws StopWalkException, IOException {
                        boolean flag = true;
                        if (StringUtils.isNotEmpty(commiter)) {
                            flag = cmit.getAuthorIdent().getName().equals(commiter);

                        }
                        if (flag && StringUtils.isNotEmpty(message)) {
                            flag = cmit.getFullMessage().contains(message);
                        }
                        return flag;
                    }

                    @Override
                    public RevFilter clone() {
                        return this;
                    }
                };
                filters.add(filter);

            }
            int skip = 0;
            // 跳过要放在maxCount前面，否则影响返回结果
            if (pageNum != null && pageNum > 0) {
                skip = (pageNum - 1) * pageSize;
                filters.add(SkipRevFilter.create(skip));
                // logCommand.setSkip((pageNum - 1) * pageSize); // 跳过的条数
            }
            int maxCount = 0;
            if (pageSize != null && pageSize > 0) {
                maxCount = pageSize + 1;
                filters.add(MaxCountRevFilter.create(maxCount));
                // logCommand.setMaxCount(pageSize); // 最大条数
            }
            if (ListUtils.isNotEmpty(filters)) {
                // logCommand.setRevFilter(AndRevFilter.create(filters));
                logCommand.setRevFilter(AndRevFilter.create(filters));
            }
            Iterable<RevCommit> logIterable = logCommand.call();
            Iterator<RevCommit> logIterator = logIterable.iterator();// 获取所有版本号的迭代器

            if (logIterator == null) {
                throw new JgitNologException("该项目暂无日志记录");
            }

            List<Ref> refs = repo.getRefDatabase().getRefs();
            while (logIterator.hasNext()) {
                RevCommit commit = logIterator.next();
                GitCommitInfoModel commitInfo = getCommitInfo(commit);
                commitInfo.setProject(projectName);
                // 添加版本信息，此操作会增加3倍左右耗时
                try {
                    RevCommit targetCommit = walk.parseCommit(repo.resolve(commit.getName() + "^0"));
                    commitInfo.setBranch(getBranchName(refs, walk, targetCommit)); // 分支名称需要获取
                } catch (Exception e) {
                    log.error("", e);
                }

                list.add(commitInfo);
            }
        }
        return list;
    }

    public static List<GitCommitInfoModel> getGitVersions(String localPath, String projectName, String commiter)
            throws GitAPIException, JgitNologException, IOException {
        return getGitVersions(localPath, projectName, QueryParams.builder().commiter(commiter).build());
    }

    /**
     * 将RevCommit封装为提交信息model
     * 
     * @Title getCommitInfo
     * @author 吕凯
     * @date 2021年6月9日 下午2:08:43
     * @param commit
     * @return GitCommitInfoModel
     */
    private static GitCommitInfoModel getCommitInfo(RevCommit commit) {
        String commitID = commit.getName(); // 提交的版本号（之后根据这个版本号去获取对应的详情记录）

        Date commitDate = commit.getAuthorIdent().getWhen(); // 提交时间
        String commitPerson = commit.getAuthorIdent().getName(); // 提交人
        String email = commit.getAuthorIdent().getEmailAddress();

        // 获取当前commit信息
        String commitTime = DateUtil.getDateTimeString(commitDate);
        String commitMessage = commit.getFullMessage();

        GitCommitInfoModel commitInfo = new GitCommitInfoModel();
        commitInfo.setId(commitID); // ObjectId.toString(revCommit.getId())需要转换，直接取出来是对象
        commitInfo.setEmail(email); // 提交人邮箱
        commitInfo.setAuthor(commitPerson);

        // 查询父级（上一条）记录
        RevCommit[] parCommits = commit.getParents();
        List<String> parentIds = ListUtils.newArrayList();
        for (RevCommit pcommit : parCommits) {
            parentIds.add(pcommit.getName());
        }
        commitInfo.setParentId(parentIds);

        commitInfo.setCommitTime(commitTime);
        commitInfo.setCommitMessage(commitMessage);
        if (commitMessage.contains("Merge") || parentIds.size() > 1) { // 从提交记录看有多个父类就是合并，此结论并不严谨，待验证
            commitInfo.setIsMerge(1);
        } else {
            commitInfo.setIsMerge(0);
        }
        return commitInfo;
    }

    /**
     * 获取提交记录的分支信息
     * 
     * @Title getBranch
     * @author 吕凯
     * @date 2021年6月9日 上午10:30:13
     * @param refs
     * @param repo
     * @param walk
     * @param commit
     * @return
     * @throws IOException
     *             String
     */
    public static List<String> getBranchName(List<Ref> refs, RevWalk walk, RevCommit targetCommit) throws IOException {
        List<String> list = ListUtils.newArrayList();
        for (Ref ref : refs) {
            // head是本地，remote是远程
            if ((ref.getName().startsWith(Constants.R_HEADS) || ref.getName().startsWith(Constants.R_REMOTES))
                    && walk.isMergedInto(targetCommit, walk.parseCommit(ref.getObjectId()))) {
                list.add(ref.getName().replace(Constants.R_HEADS, "").replace(Constants.R_REMOTES, ""));
            }
        }
        return list;
    }

    /**
     * 获取分支名称
     * 
     * @Title getBranchName
     * @author 吕凯
     * @date 2021年6月10日 下午7:29:51
     * @param repo
     * @param walk
     * @param targetCommit
     * @return
     * @throws IOException
     *             List<String>
     */
    public static List<String> getBranchName(Repository repo, RevWalk walk, RevCommit targetCommit) throws IOException {
        List<Ref> refs = repo.getRefDatabase().getRefs();
        return getBranchName(refs, walk, targetCommit);
    }

    public static List<String> getBranchName(Repository repo, RevWalk walk, String commitId) throws IOException {
        List<Ref> refs = repo.getRefDatabase().getRefs();
        RevCommit targetCommit = walk.parseCommit(repo.resolve(commitId));
        return getBranchName(refs, walk, targetCommit);
    }

    public static List<String> getBranchName(Repository repo, String commitId) throws IOException {
        try (RevWalk walk = new RevWalk(repo);) {
            return getBranchName(repo, walk, commitId);
        }
    }

    /**
     * 根据提交人获取提交记录
     * 
     * @Title getGitVersions
     * @author 吕凯
     * @date 2021年6月9日 上午9:25:24
     * @param localPath
     * @param commiter
     * @return
     * @throws GitAPIException
     * @throws JgitNologException
     * @throws IOException
     *             List<GitCommitInfoModel>
     */
    public static List<GitCommitInfoModel> getGitVersions(String localPath, String projectName)
            throws GitAPIException, JgitNologException, IOException {
        return getGitVersions(localPath, projectName, QueryParams.builder().build());
    }

    public static List<GitCommitInfoModel> getGitVersions(String localPath) throws GitAPIException, JgitNologException, IOException {
        return getGitVersions(localPath, null);
    }

    /**
     * 分页获取提交信息
     * 
     * @Title getGitVersions
     * @author 吕凯
     * @date 2021年6月15日 下午5:57:22
     * @param localPath
     * @param projectName
     * @param beginCommitId
     * @param pageSize
     * @return
     * @throws GitAPIException
     * @throws JgitNologException
     * @throws IOException
     *             List<GitCommitInfoModel>
     */
    public static List<GitCommitInfoModel> getGitVersions(String localPath, String projectName, String branch, Integer pageNum,
            Integer pageSize) throws GitAPIException, JgitNologException, IOException {
        return getGitVersions(localPath, projectName, QueryParams.builder().branch(branch).pageNum(pageNum).pageSize(pageSize).build());
    }

    /**
     * 获取某个文件的提交历史
     * 
     * @Title getFileVersions
     * @author 吕凯
     * @date 2021年6月9日 下午2:12:47
     * @param localPath
     * @param filePath
     * @return
     * @throws IOException
     *             List<GitCommitInfoModel>
     * @throws JgitNologException
     * @throws GitAPIException
     * @throws NoHeadException
     */
    public static List<GitCommitInfoModel> getFileVersions(String localPath, String filePath)
            throws IOException, JgitNologException, GitAPIException {
        List<GitCommitInfoModel> list = new ArrayList<>();
        try (Git git = new Git(new FileRepository(localPath + "/" + GIT));
                Repository repo = git.getRepository();
                RevWalk walk = new RevWalk(repo);) {
            LogCommand logCommand = git.log().addPath(filePath).all();
            Iterable<RevCommit> logIterable = logCommand.call();
            Iterator<RevCommit> logIterator = logIterable.iterator();// 获取所有版本号的迭代器

            if (logIterator == null) {
                throw new JgitNologException("该项目暂无日志记录");
            }

            List<Ref> refs = repo.getRefDatabase().getRefs();
            while (logIterator.hasNext()) {
                RevCommit commit = logIterator.next();
                GitCommitInfoModel commitInfo = getCommitInfo(commit);
                // 添加版本信息，此操作会增加3倍左右耗时
                try {
                    RevCommit targetCommit = walk.parseCommit(repo.resolve(commit.getName() + "^0"));
                    commitInfo.setBranch(getBranchName(refs, walk, targetCommit)); // 分支名称需要获取
                } catch (Exception e) {
                    log.error("", e);
                }

                list.add(commitInfo);
            }
        }
        return list;
    }

    /**
     * 根据版本号获取变化信息
     * 
     * @Title getChangedFileList
     * @author 吕凯
     * @date 2021年6月9日 下午3:37:38
     * @param commitId
     * @param repo
     * @param walk
     * @return
     * @throws RevisionSyntaxException
     * @throws IOException
     *             List<DiffEntry>
     */
    public static List<DiffEntry> getChangedFileList(String commitId, Repository repo, RevWalk walk)
            throws RevisionSyntaxException, IOException {
        ObjectId versionId = repo.resolve(commitId);
        RevCommit verCommit = walk.parseCommit(versionId);
        return getChangedFileList(verCommit, repo);
    }

    /**
     * 获取本地仓库路径某个版本号的变化信息
     * 
     * @Title getChangedFileList
     * @author 吕凯
     * @date 2021年6月9日 下午3:36:38
     * @param localRepository
     * @param commitId
     * @return
     * @throws RevisionSyntaxException
     * @throws IOException
     *             List<DiffEntry>
     */
    public static GitCommitChangeModel getChangedFileList(String localRepository, String commitId)
            throws RevisionSyntaxException, IOException {
        GitCommitChangeModel model = new GitCommitChangeModel();
        List<DiffEntry> diffFix = ListUtils.newArrayList();
        String versionCommit = commitId;// 需要分析的Commit Hash
        FileRepositoryBuilder builder = newFileRepoBuilder(localRepository);

        try (Repository repo = builder.build(); RevWalk walk = new RevWalk(repo);) {
            ObjectId versionId = repo.resolve(versionCommit);
            RevCommit verCommit = walk.parseCommit(versionId);

            model.setInfo(getCommitInfo(verCommit));

            diffFix = GitUtil.getChangedFileList(verCommit, repo);
        } catch (IOException e) {
            log.warn("", e);
        }
        model.setDiff(diffFix);
        return model;
    }

    /**
     * 
     * 根据提交记录获取文件内容
     * 
     * @Title readFile2OutputStreamFromCommit
     * @author 吕凯
     * @date 2021年6月10日 下午2:45:18
     * @param localRepository
     * @param commitId
     * @param filePath
     * @param out
     * @throws RevisionSyntaxException
     * @throws IOException
     *             void
     */
    public static void readFile2OutputStreamFromCommit(String localRepository, String commitId, String filePath, OutputStream out)
            throws RevisionSyntaxException, IOException {
        FileRepositoryBuilder builder = newFileRepoBuilder(localRepository); // 对应项目在本地Repo的路径
        try (Repository repository = builder.build(); RevWalk revWalk = new RevWalk(repository)) {
            // find the HEAD

            // a RevWalk allows to walk over commits based on some filtering that is defined
            outputFile(commitId, filePath, out, repository, revWalk);

            revWalk.dispose();
        }
    }

    /**
     * 输出文件到流
     * 
     * @Title outputFile
     * @author 吕凯
     * @date 2021年6月10日 下午2:43:41
     * @param commitId
     * @param filePath
     * @param out
     * @param repository
     * @param revWalk
     * @throws AmbiguousObjectException
     * @throws IncorrectObjectTypeException
     * @throws IOException
     * @throws MissingObjectException
     * @throws CorruptObjectException
     *             void
     */
    private static void outputFile(String commitId, String filePath, OutputStream out, Repository repository, RevWalk revWalk)
            throws IOException {
        ObjectId lastCommitId = repository.resolve(commitId);
        RevCommit commit = revWalk.parseCommit(lastCommitId);
        // and using commit's tree find the path
        RevTree tree = commit.getTree();

        // now try to find a specific file
        try (TreeWalk treeWalk = new TreeWalk(repository)) {
            treeWalk.addTree(tree);
            treeWalk.setRecursive(true);
            treeWalk.setFilter(PathFilter.create(filePath));
            if (!treeWalk.next()) {
                throw new IllegalStateException("Did not find expected file '" + filePath + "'");
            }

            ObjectId objectId = treeWalk.getObjectId(0);
            ObjectLoader loader = repository.open(objectId);

            // and then one can the loader to read the file
            loader.copyTo(out);
        }
    }

    /**
     * 读取某个文件2次提交的内容
     * 
     * @Title readFile2ContentFromCommit
     * @author 吕凯
     * @date 2021年6月10日 下午2:54:51
     * @param localRepository
     * @param commitIdOld
     * @param commitIdNew
     * @param filePath
     * @return
     * @throws RevisionSyntaxException
     * @throws IOException
     *             List<String>
     */
    public static File2CommitChangeModel readFile2ContentFromCommit(String localRepository, String commitIdOld, String commitIdNew,
            String filePath) throws RevisionSyntaxException, IOException {
        File2CommitChangeModel model = new File2CommitChangeModel();
        FileRepositoryBuilder builder = newFileRepoBuilder(localRepository);
        try (Repository repository = builder.build();
                RevWalk revWalk = new RevWalk(repository);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();) {
            // find the HEAD

            // a RevWalk allows to walk over commits based on some filtering that is defined
            outputFile(commitIdOld, filePath, baos, repository, revWalk);
            // 向OutPutStream中写入，如 message.writeTo(baos);
            String objectOld = baos.toString();
            model.setOldContent(objectOld);
            // 清空，继续写
            baos.reset();
            outputFile(commitIdNew, filePath, baos, repository, revWalk);
            String objectNew = baos.toString();
            model.setNewContent(objectNew);

            revWalk.dispose();
        }
        return model;
    }

    /**
     * blame文件,是根据当前分支blame
     * 
     * @Title BlameFile
     * @author 吕凯
     * @date 2021年6月10日 下午4:14:24
     * @param localRepository
     * @param filePath
     * @throws IOException
     * @throws GitAPIException
     *             void
     */
    public static List<FileBlameModel> blameFile(String localRepository, String filePath) throws IOException, GitAPIException {
        FileRepositoryBuilder builder = newFileRepoBuilder(localRepository); // 对应项目在本地Repo的路径
        try (Repository repo = builder.build(); Git git = new Git(repo);) {
            return blameFile(git, filePath);
        }
    }

    public static List<FileBlameModel> blameFile(Repository repo, String filePath) throws IOException, GitAPIException {
        try (Git git = new Git(repo);) {
            return blameFile(git, filePath);
        }

    }

    public static List<FileBlameModel> blameFile(Git git, String filePath) throws IOException, GitAPIException {
        List<FileBlameModel> list = ListUtils.newArrayList();
        final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("YYYY-MM-dd HH:mm");
        if (!new File(git.getRepository().getDirectory(), filePath).isDirectory()) { // 可能已经删除
            System.out.println("Blaming " + filePath);

            final BlameResult result = git.blame().setFilePath(filePath).setTextComparator(RawTextComparator.WS_IGNORE_ALL).call();
            if (result != null) { // 新增的没有result

                final RawText rawText = result.getResultContents();
                for (int i = 0; i < rawText.size(); i++) {
                    final PersonIdent sourceAuthor = result.getSourceAuthor(i);
                    final RevCommit sourceCommit = result.getSourceCommit(i);
                    FileBlameModel model = new FileBlameModel();
                    model.setInfo(getCommitInfo(sourceCommit));
                    model.setPersion(sourceAuthor);
                    model.setContent(rawText.getString(i));
                    list.add(model);
                    System.out.println(sourceAuthor.getName() + (sourceCommit != null
                            ? " - " + DATE_FORMAT.format(((long) sourceCommit.getCommitTime()) * 1000) + " - " + sourceCommit.getName()
                            : "") + ": " + rawText.getString(i));
                }
            }
        }

        return list;
    }

    /**
     * 切换分支，首先判断本地是否已有此分支
     * 
     * @Title switchBranch
     * @author 吕凯
     * @date 2021年6月7日 下午3:01:55
     * @param localPath
     *            本地仓库地址
     * @param branch
     *            branch为简短名称
     * @return String
     */
    public static String switchBranch(String localPath, String branch) {
        try (Git git = Git.open(new File(localPath));) {

            CheckoutCommand checkoutCommand = git.checkout();
            // 如果本地分支不存在，先创建
            if (!checkLocalBranchName(git, branch)) {
                checkoutCommand.setCreateBranch(true).setStartPoint("origin/" + branch);
                /*                checkoutCommand.setStartPoint(branch);
                checkoutCommand.setCreateBranch(true);*/
            }
            checkoutCommand.setName(branch);
            checkoutCommand.call();
            return null;
        } catch (Exception e) {
            log.error("", e);
            return e.getMessage();
        }
    }

    /**
     * 切换分支，首先判断本地是否已有此分支
     * 
     * @Title switchBranch
     * @author 吕凯
     * @date 2021年6月7日 下午3:01:55
     * @param localPath
     *            本地仓库地址
     * @param branch
     * @return String
     */
    public static String deleteLocalBranch(String localPath, String branch) {
        try (Git git = Git.open(new File(localPath));) {

            // 如果本地分支
            if (checkLocalBranchName(git, branch)) {
                // delete branch 'branchToDelete' locally //
                git.branchDelete().setBranchNames(branch).call();

                // 删除远程分支
                // delete branch 'branchToDelete' on remote 'origin'
                /*RefSpec refSpec = new RefSpec()
                        .setSource(null)
                        .setDestination("refs/heads/branchToDelete");
                git.push().setRefSpecs(refSpec).setRemote('origin').call();*/
            }
            return null;
        } catch (Exception e) {
            log.error("", e);
            return e.getMessage();
        }
    }

    /**
     * 获取本地所有名称，里面包含简短名称（去掉refs/heads/和refs/remotes/的）和完整名称，
     * 
     * @Title getBranchShortNames
     * @author 吕凯
     * @date 2021年6月7日 下午3:01:05
     * @param localPath
     * @return
     * @throws IOException
     * @return List<String>
     */
    public static BranchNameList getBranchNames(String localPath) throws IOException {
        try (Git git = Git.open(new File(localPath));) {
            return getBranchNames(git);
        }
    }

    public static BranchNameList getBranchNames(Git git) throws IOException {
        return getBranchNames(git.getRepository());
    }

    /**
     * 获取所有的分支
     * 
     * @Title getBranchNames
     * @author 吕凯
     * @date 2021年6月11日 下午1:58:37
     * @param repo
     * @return
     * @throws IOException
     *             BranchNameList
     */
    public static BranchNameList getBranchNames(Repository repo) throws IOException {
        BranchNameList nameList = new BranchNameList();
        List<BranchNameModel> localList = ListUtils.newArrayList();
        List<BranchNameModel> remoteList = ListUtils.newArrayList();
        List<BranchNameModel> tagList = ListUtils.newArrayList();
        List<Ref> refs = repo.getRefDatabase().getRefs();
        for (Ref ref : refs) {
            String refName = ref.getName();
            // head是本地，remote是远程
            if (refName.startsWith(Constants.R_HEADS)) { // 需要进行筛选
                String branchName = refName.replace(Constants.R_HEADS, "");
                BranchNameModel model = new BranchNameModel();
                model.setFullName(refName);
                model.setShortName(branchName);
                localList.add(model);
            }
            if (refName.startsWith(Constants.R_REMOTES)) { // 需要进行筛选
                String branchName = refName.replace(Constants.R_REMOTES, "");
                BranchNameModel model = new BranchNameModel();
                model.setFullName(refName);
                model.setShortName(branchName);
                remoteList.add(model);
            }
            if (refName.startsWith(Constants.R_TAGS)) { // 需要进行筛选
                String branchName = refName.replace(Constants.R_TAGS, "");
                BranchNameModel model = new BranchNameModel();
                model.setFullName(refName);
                model.setShortName(branchName);
                tagList.add(model);
            }
        }
        nameList.setLocalBranchs(localList);
        nameList.setRemoteBranchs(remoteList);
        nameList.setTags(tagList);
        return nameList;
    }

    public static boolean checkLocalBranchName(String localPath, String branchName) throws IOException {
        try (Git git = Git.open(new File(localPath));) {
            return checkLocalBranchName(git, branchName);
        }
    }

    public static boolean checkLocalBranchName(Git git, String branchName) throws IOException {
        return checkLocalBranchName(git.getRepository(), branchName);
    }

    /**
     * 检查是否存在本地分支
     * 
     * @Title checkLocalBranchNames
     * @author 吕凯
     * @date 2021年6月11日 下午2:09:37
     * @param repo
     * @param branchName
     *            本地分支名字
     * @return
     * @throws IOException
     *             boolean
     */
    public static boolean checkLocalBranchName(Repository repo, String branchName) throws IOException {
        BranchNameList namelist = getBranchNames(repo);
        if (StringUtils.isNotEmpty(branchName) && namelist != null && ListUtils.isNotEmpty(namelist.getLocalBranchs())) {
            return namelist.getLocalBranchs().stream().filter(nameModel -> nameModel.getShortName().equals(branchName)).findAny()
                    .isPresent();
        }
        return false;
    }

}